﻿//---------------------------------------------------------------------
// Copyright (c) 2007 Digerati Technologies, LLC.  All Rights Reserved.
//
// Release under the Microsoft Public License (Ms-PL).
//---------------------------------------------------------------------

using System;
using System.Management;
using System.ServiceProcess;

namespace DigeratiTech.NDiagnostics
{
    /// <summary>
    /// This <see cref="DiagnosticTask"/> verifies that the specified Windows Service is installed and in the proper state.
    /// </summary>
    public class WindowsServiceTask : DiagnosticTask
    {
        #region Properties...

        /// <summary>
        /// Gets or sets the name of the Windows Service to be validated.
        /// </summary>
        /// <value>The name of the Windows Service to be validated.</value>
        public string ServiceName { get; set; }

        /// <summary>
        /// Gets or sets the Windows Service status to be validated.
        /// </summary>
        /// <value>The Windows Service status to be validated.</value>
        public string ServiceStatus { get; set; }

        /// <summary>
        /// Gets or sets the Windows Service Startup Type to be validated.
        /// </summary>
        /// <value>The Windows Service Startup Type to be validated.</value>
        public string StartupType { get; set; }

        /// <summary>
        /// Gets or sets the Windows Service Startup Account to be validated.
        /// </summary>
        /// <value>The Windows Service Startup Account to be validated.</value>
        public string StartupAccount { get; set; }

        #endregion

        #region Constructors...

        /// <summary>
        /// Initializes a new instance of the <see cref="WindowsServiceTask"/> class.
        /// </summary>
        /// <param name="group">The <see cref="TaskGroup"/> this task belongs to.</param>
        /// <param name="name">The task name.</param>
        /// <param name="serviceName">Name of the Windows Service to be validated.</param>
        /// <param name="serviceStatus">The expected Windows Service status.</param>
        /// <param name="startupType">The expected Windows Service startup type.</param>
        /// <param name="startupAccount">The expected Windows Service startup account.</param>
        public WindowsServiceTask(TaskGroup group, string name, string serviceName, string serviceStatus, string startupType, string startupAccount)
        {
            taskGroup = group;
            Name = name;
            ServiceName = serviceName;
            ServiceStatus = serviceStatus;
            StartupType = startupType;
            StartupAccount = startupAccount;
            Status = TaskStatus.NotRun;
        }

        #endregion

        #region Methods...

        /// <summary>
        /// Executes the task.
        /// </summary>
        /// <returns>
        /// 	<c>true</c> if the task is successful; otherwise, <c>false</c>.
        /// </returns>
        public override bool Execute()
        {
            ServiceController controller = GetServiceController(ServiceName);

            Status = TaskStatus.Completed;
            Message = string.Empty;

            // Check to see if the Windows Service is installed
            if (controller == null)
            {
                Status = TaskStatus.Error;
                Message = (Message.Length > 0 ? "\n" : "") + "The Windows Service '" + ServiceName + "' could not be found.";
            }
            else
            {
                // Do not process ServiceStatus if it's not set
                if (!string.IsNullOrEmpty(ServiceStatus))
                {
                    // Check the Windows Service status
                    if (!controller.Status.ToString().Equals(ServiceStatus, StringComparison.CurrentCultureIgnoreCase))
                    {
                        Status = TaskStatus.Error;
                        Message += (Message.Length > 0 ? "\n" : "") + "The Windows Server status should be '" + ServiceStatus + "' but is currently '" + controller.Status.ToString() + "'.";
                    }
                }

                // Do not process StartupType if it's not set
                if (!string.IsNullOrEmpty(StartupType))
                {
                    string startupType = GetStartupType(ServiceName);

                    // Check the Windows Service Startup Type
                    if (!startupType.Equals(StartupType, StringComparison.CurrentCultureIgnoreCase))
                    {
                        Status = TaskStatus.Error;
                        Message += (Message.Length > 0 ? "\n" : "") + string.Format("The Windows Service Startup Type should be '{0}' but is currently '{1}'.", StartupType, startupType);
                    }
                }

                // Do not process StartupAccount if it's not set
                if (!string.IsNullOrEmpty(StartupAccount))
                {
                    string startupAccount = GetStartupAccount(ServiceName);

                    // Check the Windows Service Startup Account
                    if (!startupAccount.Equals(StartupAccount, StringComparison.CurrentCultureIgnoreCase))
                    {
                        Status = TaskStatus.Error;
                        Message += (Message.Length > 0 ? "\n" : "") + string.Format("The Windows Service Startup Account should be '{0}' but is currently '{1}'.", StartupAccount, startupAccount);
                    }
                }
            }

            return (Message.Length == 0);
        }

        /// <summary>
        /// Gets the Windows Service controller for the specified service name.
        /// </summary>
        /// <param name="serviceName">The name of the Windows Service to return a <see cref="ServiceController"/> for.</param>
        /// <returns>The <see cref="ServiceController"/> for the specified <paramref name="serviceName"/> if found; otherwise, null.</returns>
        private ServiceController GetServiceController(string serviceName)
        {
            ServiceController serviceController = null;

            foreach (ServiceController controller in ServiceController.GetServices())
            {
                if (controller.ServiceName.Equals(serviceName, StringComparison.CurrentCultureIgnoreCase))
                {
                    serviceController = controller;
                    break;
                }
            }

            return serviceController;
        }

        /// <summary>
        /// Gets the Startup Type (e.g. Boot, System, Auto, Manual, or Disabled)
        /// for the specified Windows service.
        /// </summary>
        /// <param name="serviceName">The Windows Service name.</param>
        /// <returns>The Startup Type for the specified Windows Service.</returns>
        private string GetStartupType(string serviceName)
        {
            string startupType = string.Empty;

            ManagementClass mgmtClass = new ManagementClass("WIN32_Service");

            foreach (ManagementObject mgmtObject in mgmtClass.GetInstances())
            {
                if (mgmtObject.GetPropertyValue("Name").ToString().Equals(serviceName, StringComparison.CurrentCultureIgnoreCase))
                {
                    startupType = mgmtObject.GetPropertyValue("StartMode").ToString();
                    break;
                }
            }

            return startupType;
        }

        /// <summary>
        /// Gets the Startup Account (i.e. the account the Windows Service is running under)
        /// for the specified Windows service.
        /// </summary>
        /// <param name="serviceName">The Windows Service name.</param>
        /// <returns>The Startup Account for the specified Windows Service.</returns>
        private string GetStartupAccount(string serviceName)
        {
            string startupAccount = string.Empty;

            ManagementClass mgmtClass = new ManagementClass("WIN32_Service");

            foreach (ManagementObject mgmtObject in mgmtClass.GetInstances())
            {
                if (mgmtObject.GetPropertyValue("Name").ToString().Equals(serviceName, StringComparison.CurrentCultureIgnoreCase))
                {
                    startupAccount = mgmtObject.GetPropertyValue("StartName").ToString();
                    break;
                }
            }

            return startupAccount;
        }

        #endregion
    }
}
